"""
均线金叉死叉算法验证工具 (PyEcharts增强可视化版)
功能：验证趋势追踪止损中使用的均线上穿下穿算法
输出：生成一个列表，标记每个K线周期的金叉(上穿)、死叉(下穿)或无信号状态
可视化：使用PyEcharts生成交互式K线图和双均线副图，在副图上用上下箭头标注金死叉
特点：
1. 主图显示K线走势
2. 副图显示双均线(Trading Line和Short Line)
3. 金叉(上穿)用绿色上箭头标注
4. 死叉(下穿)用红色下箭头标注
5. 鼠标悬停可查看详细信息
"""

import numpy as np
import pandas as pd
# matplotlib已被注释，我们使用pyecharts进行可视化
# import matplotlib.pyplot as plt
from datetime import datetime
import argparse
from dateutil.relativedelta import relativedelta
from pyecharts import options as opts
from pyecharts.charts import Kline, Line, Bar, Grid, Scatter
from pyecharts.components import Table
from pyecharts.options import ComponentTitleOpts
from MyTT import REF, EMA
from tqsdk import TqApi, TqAuth, TqKq
from algorithm_module import calculate_boeyi_indicators, calculate_trading_signals, safe_date_format, SUM, MA, STD, MACD

# 新增博易大师技术指标算法已从模块导入，下方函数定义已移除
    

# 修改计算交易信号函数，整合博易大师算法
def calculate_trading_signals(klines):
    """
    计算交易信号，整合博易大师技术指标算法
    """
    trading_line, short_line, signals = calculate_boeyi_indicators(klines)
    
    open_price = klines.open
    high = klines.high
    low = klines.low
    
    # 创建结果DataFrame
    result = pd.DataFrame({
        'datetime': klines.datetime,
        'open': open_price,
        'high': high,
        'low': low,
        'close': klines.close,
        'trading_line': trading_line,
        'short_line': short_line,
        'signal': signals.map({
            1: '上穿',
            -1: '下穿',
        }).fillna(np.nan)
    })
    
    # 处理日期格式
    result['date'] = result['datetime']
    result['date_str'] = [d.strftime('%Y-%m-%d') if hasattr(d, 'strftime') else str(d) for d in result['date']]
    
    return result

# 安全的日期格式化函数
def safe_date_format(dt_value):
    """
    安全地格式化日期时间值，处理各种可能的格式
    增强版：处理 akshare 返回的 datetime.date 对象和其他可能的日期格式
    """
    if pd.isna(dt_value):
        return "N/A"
    
    try:
        # 如果是 datetime.date、pandas Timestamp 或 datetime 对象
        if hasattr(dt_value, 'strftime'):
            return dt_value.strftime('%Y-%m-%d')  # 只显示日期部分，不显示时间
        
        # 如果是字符串，尝试解析
        if isinstance(dt_value, str):
            try:
                return pd.to_datetime(dt_value).strftime('%Y-%m-%d')
            except Exception:
                pass
        
        # 如果是时间戳（整数或浮点数）
        if isinstance(dt_value, (int, float)):
            try:
                return datetime.fromtimestamp(dt_value).strftime('%Y-%m-%d')
            except Exception:
                pass
    except Exception:
        pass
        
    # 如果所有尝试都失败，返回原始值的字符串表示
    return str(dt_value)

def create_pyecharts_visualization(result, symbol, period_name):
    """
    使用PyEcharts创建可视化图表
    增强版：K线图和双均线副图，使用金叉死叉箭头标注
    """
    # 准备数据 - 使用预先格式化的日期字符串
    date_list = result['date_str'].tolist()
    
    # K线数据格式：[开盘价, 收盘价, 最低价, 最高价]
    # PyEcharts K线图要求的顺序是 [open, close, lowest, highest]
    kline_data = []
    for i in range(len(result)):
        try:
            open_price = float(result['open'].iloc[i])
            close_price = float(result['close'].iloc[i])
            low_price = float(result['low'].iloc[i])
            high_price = float(result['high'].iloc[i])
            
            # 打印前5个K线数据，用于调试
            if i < 5:
                print(f"K线数据 {i}: 开盘={open_price}, 收盘={close_price}, 最低={low_price}, 最高={high_price}")
            
            kline_data.append([open_price, close_price, low_price, high_price])
        except Exception as e:
            print(f"处理K线数据时出错，索引 {i}: {e}")
            # 使用默认值
            kline_data.append([0, 0, 0, 0])
    
    trading_line_data = [float(val) if not pd.isna(val) else None for val in result['trading_line']]
    short_line_data = [float(val) if not pd.isna(val) else None for val in result['short_line']]
    
    # 准备上穿下穿点数据（金叉死叉）
    up_cross = result[result['signal'] == '上穿']
    down_cross = result[result['signal'] == '下穿']
    
    # 使用索引位置而不是日期字符串来定位点，避免日期格式化问题
    try:
        # 尝试使用索引方法
        up_cross_indices = up_cross.index.tolist()
        up_cross_x = [i for i in range(len(result)) if i in up_cross_indices]
        up_cross_y = [float(val) for val in up_cross['trading_line']]
        up_cross_dates = [result['date_str'].iloc[i] if i < len(result) else f"数据点-{i}" for i in up_cross_indices]
        up_cross_values = [f"金叉: {val:.4f}" for val in up_cross['trading_line']]
        
        down_cross_indices = down_cross.index.tolist()
        down_cross_x = [i for i in range(len(result)) if i in down_cross_indices]
        down_cross_y = [float(val) for val in down_cross['trading_line']]
        down_cross_dates = [result['date_str'].iloc[i] if i < len(result) else f"数据点-{i}" for i in down_cross_indices]
        down_cross_values = [f"死叉: {val:.4f}" for val in down_cross['trading_line']]
    except Exception as e:
        print(f"处理交叉点数据时出错: {e}")
        print("使用备用方法...")
        
        # 备用方法：直接使用行号
        up_cross_x = up_cross.index.tolist()
        up_cross_y = [float(val) if not pd.isna(val) else 0 for val in up_cross['trading_line']]
        up_cross_dates = [f"上穿点-{i}" for i in range(len(up_cross))]
        up_cross_values = [f"金叉: {val:.4f}" for val in up_cross_y]
        
        down_cross_x = down_cross.index.tolist()
        down_cross_y = [float(val) if not pd.isna(val) else 0 for val in down_cross['trading_line']]
        down_cross_dates = [f"下穿点-{i}" for i in range(len(down_cross))]
        down_cross_values = [f"死叉: {val:.4f}" for val in down_cross_y]
    
    # 创建K线图
    kline = (
        Kline()
        .add_xaxis(date_list)
        .add_yaxis(
            "K线",
            kline_data,
            itemstyle_opts=opts.ItemStyleOpts(
                color="#ec0000",  # 阳线颜色
                color0="#00da3c",  # 阴线颜色
                border_color="#8A0000",  # 阳线边框颜色
                border_color0="#008F28",  # 阴线边框颜色
            ),
        )
        .set_global_opts(
            title_opts=opts.TitleOpts(
                title=f"{symbol} {period_name}       K线图",
                subtitle="趋势追踪止损策略验证",
                pos_left="center",
                title_textstyle_opts=opts.TextStyleOpts(
                    font_size=16,
                    font_weight="bold"
                ),
                subtitle_textstyle_opts=opts.TextStyleOpts(
                    font_size=12
                )
            ),
            xaxis_opts=opts.AxisOpts(
                type_="category",
                is_scale=True,
                boundary_gap=False,
                axisline_opts=opts.AxisLineOpts(is_on_zero=False),
                splitline_opts=opts.SplitLineOpts(is_show=False),
                split_number=20,
                min_="dataMin",
                max_="dataMax",
                axislabel_opts=opts.LabelOpts(
                    is_show=False  # 不显示日期标签
                ),
            ),
            yaxis_opts=opts.AxisOpts(
                is_scale=True,
                splitline_opts=opts.SplitLineOpts(is_show=True),
                axislabel_opts=opts.LabelOpts(formatter="{value}"),
            ),
            tooltip_opts=opts.TooltipOpts(
                trigger="axis", 
                axis_pointer_type="cross",
                background_color="rgba(245, 245, 245, 0.8)",
                border_width=1,
                border_color="#ccc",
                textstyle_opts=opts.TextStyleOpts(color="#000"),
                formatter="""
                function(params) {
                    const date = params[0].name;
                    let res = `<div style="font-weight:bold;margin-bottom:3px;">${date}</div>`;
                    
                    for (let i = 0; i < params.length; i++) {
                        const param = params[i];
                        const color = param.color;
                        const seriesName = param.seriesName;
                        let value = '';
                        
                        if (seriesName === 'K线') {
                            // K线数据格式是[open, close, low, high]
                            // 确保数据存在且格式正确
                            if (param.data && param.data.length >= 4) {
                                value = `开盘: ${param.data[0]}<br/>
                                        收盘: ${param.data[1]}<br/>
                                        最低: ${param.data[2]}<br/>
                                        最高: ${param.data[3]}`;
                            } else {
                                value = '数据格式错误';
                            }
                        } else {
                            value = param.data;
                            if (typeof value === 'number') {
                                value = value.toFixed(4);
                            }
                        }
                        
                        res += `<div style="display:flex;align-items:center;margin-top:3px;">
                                <div style="height:10px;width:10px;border-radius:50%;background-color:${color};margin-right:5px;"></div>
                                <div style="font-weight:bold;">${seriesName}: </div>
                                <div style="margin-left:5px;">${value}</div>
                                </div>`;
                    }
                    
                    return res;
                }
                """,
            ),
            datazoom_opts=[
                # 底部滑动条，同时控制主图和副图
                opts.DataZoomOpts(
                    is_show=True,
                    type_="slider",
                    pos_bottom="3%",
                    range_start=0,  # 默认显示全部数据
                    range_end=100,
                    xaxis_index=[0, 1, 2],  # 同时控制主图和两个副图的x轴
                ),
                # 主图内部缩放，同时控制主图和副图
                opts.DataZoomOpts(
                    is_show=False,
                    type_="inside",
                    range_start=0,
                    range_end=100,
                    xaxis_index=[0, 1, 2],  # 同时控制主图和两个副图的x轴
                    orient="horizontal",
                ),
                # 副图内部缩放，同时控制主图和副图
                opts.DataZoomOpts(
                    is_show=False,
                    type_="inside",
                    range_start=0,
                    range_end=100,
                    xaxis_index=[0, 1, 2],  # 同时控制主图和两个副图的x轴
                    orient="horizontal",
                ),
            ],
            legend_opts=opts.LegendOpts(
                is_show=True,
                pos_top="3%",
                pos_left="center",
                orient="horizontal",
                item_gap=25,  # 增加图例项之间的间距
            ),
        )
    )
    
    # 创建均线图（副图）
    line = (
        Line()
        .add_xaxis(date_list)
        .add_yaxis(
            "Trading Line",
            trading_line_data,
            is_smooth=True,
            linestyle_opts=opts.LineStyleOpts(width=2, color="#1E90FF"),  # 深蓝色
            label_opts=opts.LabelOpts(is_show=False),
            symbol="emptyCircle",
            symbol_size=3,
            tooltip_opts=opts.TooltipOpts(formatter="{a}: {c}"),
        )
        .add_yaxis(
            "Short Line",
            short_line_data,
            is_smooth=True,
            linestyle_opts=opts.LineStyleOpts(width=2, color="#FF4500"),  # 橙红色
            label_opts=opts.LabelOpts(is_show=False),
            symbol="emptyCircle",
            symbol_size=3,
            tooltip_opts=opts.TooltipOpts(formatter="{a}: {c}"),
        )
        .set_global_opts(
            title_opts=opts.TitleOpts(
                title=f"{symbol} {period_name} 均线信号",
                subtitle="Trading Line与Short Line",
                pos_left="center",
                pos_top="48%",  # 稍微上移标题位置
                title_textstyle_opts=opts.TextStyleOpts(
                    font_size=16,
                    font_weight="bold"
                ),
                subtitle_textstyle_opts=opts.TextStyleOpts(
                    font_size=12
                )
            ),
            xaxis_opts=opts.AxisOpts(
                type_="category",
                grid_index=1,
                is_scale=True,
                boundary_gap=False,
                axisline_opts=opts.AxisLineOpts(is_on_zero=False),
                splitline_opts=opts.SplitLineOpts(is_show=False),
                split_number=20,
                min_="dataMin",
                max_="dataMax",
                axislabel_opts=opts.LabelOpts(
                    rotate=30,  # 旋转标签以避免重叠
                    font_size=10,
                    interval="auto"  # 自动计算间隔
                ),
            ),
            yaxis_opts=opts.AxisOpts(
                grid_index=1,
                is_scale=True,
                splitline_opts=opts.SplitLineOpts(is_show=True),
                axislabel_opts=opts.LabelOpts(formatter="{value}"),
            ),
            tooltip_opts=opts.TooltipOpts(
                trigger="axis", 
                axis_pointer_type="cross",
                background_color="rgba(245, 245, 245, 0.8)",
                border_width=1,
                border_color="#ccc",
                textstyle_opts=opts.TextStyleOpts(color="#000"),
                formatter="""
                function(params) {
                    const date = params[0].name;
                    let res = `<div style="font-weight:bold;margin-bottom:3px;">${date}</div>`;
                    
                    for (let i = 0; i < params.length; i++) {
                        const param = params[i];
                        const color = param.color;
                        const seriesName = param.seriesName;
                        let value = '';
                        
                        if (seriesName === 'K线') {
                            // K线数据格式是[open, close, low, high]
                            // 确保数据存在且格式正确
                            if (param.data && param.data.length >= 4) {
                                value = `开盘: ${param.data[0]}<br/>
                                        收盘: ${param.data[1]}<br/>
                                        最低: ${param.data[2]}<br/>
                                        最高: ${param.data[3]}`;
                            } else {
                                value = '数据格式错误';
                            }
                        } else {
                            value = param.data;
                        }
                        
                        res += `<div style="display:flex;align-items:center;margin-top:3px;">
                                <div style="height:10px;width:10px;border-radius:50%;background-color:${color};margin-right:5px;"></div>
                                <div style="font-weight:bold;">${seriesName}: </div>
                                <div style="margin-left:5px;">${value}</div>
                                </div>`;
                    }
                    
                    return res;
                }
                """,
            ),
            datazoom_opts=[
                # 底部滑动条，同时控制主图和副图
                opts.DataZoomOpts(
                    is_show=True,
                    type_="slider",
                    pos_bottom="3%",
                    range_start=0,  # 默认显示全部数据
                    range_end=100,
                    xaxis_index=[0, 1, 2],  # 同时控制主图和两个副图的x轴
                ),
                # 主图内部缩放，同时控制主图和副图
                opts.DataZoomOpts(
                    is_show=False,
                    type_="inside",
                    range_start=0,
                    range_end=100,
                    xaxis_index=[0, 1, 2],  # 同时控制主图和两个副图的x轴
                    orient="horizontal",
                ),
                # 副图内部缩放，同时控制主图和副图
                opts.DataZoomOpts(
                    is_show=False,
                    type_="inside",
                    range_start=0,
                    range_end=100,
                    xaxis_index=[0, 1, 2],  # 同时控制主图和两个副图的x轴
                    orient="horizontal",
                ),
            ],
            legend_opts=opts.LegendOpts(
                is_show=True,
                pos_top="52%",
                pos_left="center",
                orient="horizontal",
                item_gap=25,  # 增加图例项之间的间距
            ),
        )
    )
    

    
    # 创建关系矩形图（第三个副图）
    # 计算操盘线和空头线的关系
    relation_data = []
    for i in range(len(result)):
        if trading_line_data[i] is not None and short_line_data[i] is not None:
            if trading_line_data[i] > short_line_data[i]:
                # 操盘线 > 空头线，用红色填充
                relation_data.append(1)  # 1表示红色
            else:
                # 操盘线 < 空头线，用绿色填充
                relation_data.append(0)  # 0表示绿色
        else:
            relation_data.append(None)
    
    # 创建关系矩形图
    bar = (
        Bar()
        .add_xaxis(date_list)
        .add_yaxis(
            "操盘线>空头线",
            [1 if x == 1 else 0 for x in relation_data],
            stack="stack1",
            category_gap="0%",
            itemstyle_opts=opts.ItemStyleOpts(color="#FF4500"),  # 红色
            label_opts=opts.LabelOpts(is_show=False),
        )
        .add_yaxis(
            "操盘线<空头线",
            [1 if x == 0 else 0 for x in relation_data],
            stack="stack1",
            category_gap="0%",
            itemstyle_opts=opts.ItemStyleOpts(color="#00FF00"),  # 绿色
            label_opts=opts.LabelOpts(is_show=False),
        )
        .set_global_opts(
            title_opts=opts.TitleOpts(
                title="操盘线与空头线关系",
                subtitle="红色：操盘线>空头线，绿色：操盘线<空头线",
                pos_left="center",
                pos_top="78%",  # 位于第三个位置
                title_textstyle_opts=opts.TextStyleOpts(
                    font_size=16,
                    font_weight="bold"
                ),
                subtitle_textstyle_opts=opts.TextStyleOpts(
                    font_size=12
                )
            ),
            xaxis_opts=opts.AxisOpts(
                type_="category",
                grid_index=2,
                is_scale=True,
                boundary_gap=True,
                axisline_opts=opts.AxisLineOpts(is_on_zero=False),
                splitline_opts=opts.SplitLineOpts(is_show=False),
                split_number=20,
                min_="dataMin",
                max_="dataMax",
                axislabel_opts=opts.LabelOpts(
                    rotate=30,  # 旋转标签以避免重叠
                    font_size=10,
                    interval="auto",  # 自动计算间隔
                    is_show=True  # 确保显示标签
                ),
            ),
            yaxis_opts=opts.AxisOpts(
                grid_index=2,
                is_scale=False,
                splitline_opts=opts.SplitLineOpts(is_show=False),
                axislabel_opts=opts.LabelOpts(is_show=False),  # 不显示Y轴标签
                max_=1,  # 固定Y轴最大值为1
                min_=0,  # 固定Y轴最小值为0
            ),
            tooltip_opts=opts.TooltipOpts(
                trigger="axis", 
                axis_pointer_type="cross",
                background_color="rgba(245, 245, 245, 0.8)",
                border_width=1,
                border_color="#ccc",
                textstyle_opts=opts.TextStyleOpts(color="#000"),
                formatter="""
                function(params) {
                    const date = params[0].name;
                    let res = `<div style="font-weight:bold;margin-bottom:3px;">${date}</div>`;
                    
                    // 检查是红色还是绿色区域
                    let relation = "";
                    let color = "";
                    
                    if (params[0].seriesName === "操盘线>空头线" && params[0].data > 0) {
                        relation = "操盘线 > 空头线";
                        color = "#FF4500";
                    } else if (params[1].seriesName === "操盘线<空头线" && params[1].data > 0) {
                        relation = "操盘线 < 空头线";
                        color = "#00FF00";
                    }
                    
                    res += `<div style="display:flex;align-items:center;margin-top:3px;">
                            <div style="height:10px;width:10px;border-radius:50%;background-color:${color};margin-right:5px;"></div>
                            <div style="font-weight:bold;">${relation}</div>
                            </div>`;
                    
                    return res;
                }
                """
            ),
            legend_opts=opts.LegendOpts(
                is_show=True,
                pos_top="82%",
                pos_left="center",
                orient="horizontal",
                item_gap=25,  # 增加图例项之间的间距
            ),
            # 添加专门的DataZoom配置，确保与其他图表同步
            datazoom_opts=[
                # 底部滑动条
                opts.DataZoomOpts(
                    is_show=False,  # 不显示额外的滑动条
                    type_="slider",
                    xaxis_index=2,  # 控制第三个图表的x轴
                    range_start=0,
                    range_end=100,
                ),
                # 内部缩放
                opts.DataZoomOpts(
                    is_show=False,
                    type_="inside",
                    xaxis_index=2,  # 控制第三个图表的x轴
                    range_start=0,
                    range_end=100,
                    orient="horizontal",
                ),
            ],
        )
    )
    
    # 创建网格布局 - 优化版，确保主图和副图完美对齐
    grid = (
        Grid(init_opts=opts.InitOpts(
            width="1280px",  # 增加宽度
            height="1200px",  # 增加高度以容纳第三个副图
            theme="white",
            animation_opts=opts.AnimationOpts(
                animation=True,
                animation_threshold=2000,
                animation_duration=1000,
                animation_easing="cubicOut"
            ),
            page_title=f"{symbol} {period_name} 金叉死叉分析 - 趋势追踪止损策略验证"
        ))
        .add(kline, grid_opts=opts.GridOpts(
            pos_left="10%", 
            pos_right="5%", 
            height="40%",  # 增加主图高度
            is_contain_label=True
        ))
        .add(line, grid_opts=opts.GridOpts(
            pos_left="10%", 
            pos_right="5%", 
            pos_top="58%",  # 调整副图位置
            height="38%",  # 增加副图高度
            is_contain_label=True
        ))
        .add(bar, grid_opts=opts.GridOpts(
            pos_left="10%", 
            pos_right="5%", 
            pos_top="88%",  # 第三个副图位置
            height="10%",  # 第三个副图高度
            is_contain_label=True  # 确保包含标签
        ))
    )
    
    # 保存为HTML文件
    html_filename = f"{symbol.replace('.', '_')}_{period_name}_分析.html"
    import os
    current_dir = os.path.dirname(os.path.abspath(__file__))
    html_path = os.path.join(current_dir, f"{symbol.replace('.', '_')}_{period_name}_分析.html")
    grid.render(html_path)
    
    return html_filename

# 主函数
print("开始验证均线上穿下穿算法...")

# 定义股票代码和周期
symbol = "sh000001"  # 上证指数
period_name = "日线"  # 周期名称，用于图表标题

# 允许用户自定义数据的时间范围
def get_date_range():
    """获取用户自定义的日期范围，如果没有指定则使用默认值"""
    parser = argparse.ArgumentParser(description='均线金叉死叉算法验证工具')
    parser.add_argument('--start', type=str, help='起始日期，格式：YYYY-MM-DD')
    parser.add_argument('--end', type=str, help='结束日期，格式：YYYY-MM-DD')
    parser.add_argument('--years', type=float, help='获取最近几年的数据，默认为2年')
    args = parser.parse_args()
    
    # 默认结束日期为2023-08-23
    end_date = pd.Timestamp('2025-08-23').date()
    
    # 如果指定了结束日期，则使用指定的结束日期
    if args.end:
        try:
            end_date = pd.Timestamp(args.end).date()
        except:
            print(f"警告：无效的结束日期格式 '{args.end}'，使用默认值 {end_date}")
    
    # 默认获取最近2年的数据
    years = 2.0
    if args.years:
        years = args.years
    
    # 计算默认的起始日期
    default_start_date = end_date - relativedelta(years=years)
    
    # 如果指定了起始日期，则使用指定的起始日期
    start_date = default_start_date
    if args.start:
        try:
            start_date = pd.Timestamp(args.start).date()
        except:
            print(f"警告：无效的起始日期格式 '{args.start}'，使用默认值 {start_date}")
    
    return start_date, end_date

try:
    # 获取用户自定义的日期范围
    start_date, cutoff_date = get_date_range()
    
    # 使用天勤量化获取 K 线数据
    # 获取上证指数的日线数据
    #api = TqApi(backtest=TqBacktest(start_dt=start_date, end_dt=cutoff_date), auth=TqAuth(os.getenv('TQ_USER'), os.getenv('TQ_PASSWORD')))
    api = TqApi(account=TqKq(), auth=TqAuth(os.getenv('TQ_USER'), os.getenv('TQ_PASSWORD')))
    import os
    klines = api.get_kline_serial("SHFE.ao2601", 86400)
    
    # 等待数据加载完成
    while True:
        api.wait_update()
        if len(klines) > 0:
            break
    
    klines_df = pd.DataFrame(klines)
    klines_df['datetime'] = pd.to_datetime(klines_df['datetime'], unit='ns')
    klines_df = klines_df[(klines_df['datetime'].dt.date <= cutoff_date) & (klines_df['datetime'].dt.date >= start_date)]
    api.close()
    
    print(f"使用截止日期: {cutoff_date}")
    print(f"使用起始日期: {start_date}")
    print(f"筛选后的数据量: {len(klines_df)} 条记录")
    
    # 重命名列以匹配代码中使用的列名
    klines_df = klines_df.rename(columns={
        'datetime': 'datetime',  # 时间列保持为datetime
        'open': 'open',      # 开盘价
        'high': 'high',      # 最高价
        'low': 'low',        # 最低价
        'close': 'close'     # 收盘价
    })
    
    # 转换为类似于原代码中使用的对象格式
    class KLineData:
        def __init__(self, df):
            # 直接使用DataFrame的Series，这样可以使用iloc属性
            self.datetime = df['datetime']
            self.open = df['open']
            self.high = df['high']
            self.low = df['low']
            self.close = df['close']
            self._length = len(df)
            self._df = df  # 保存原始DataFrame以便后续使用
        
        def __len__(self):
            return self._length
    
    klines = KLineData(klines_df)
    
    # 检查数据是否为空
    if len(klines) == 0:
        print("错误：筛选后的数据为空，请调整日期范围")
        exit(1)
    
    # 调试输出，查看日期格式
    sample_ts = klines.datetime.iloc[0]  # 使用iloc而不是索引访问
    print(f"调试信息 - 日期样本: {sample_ts}, 类型: {type(sample_ts)}")
    
    # 计算信号，添加异常处理
    try:
        result = calculate_trading_signals(klines)
    except Exception as e:
        print(f"计算信号时出错: {e}")
        print("尝试使用备用方法处理时间戳...")
        
        # 备用方法：直接使用索引作为日期标识
        close = klines.close
        open_price = klines.open
        high = klines.high
        low = klines.low
        
        # 计算Q值
        Q = (close - REF(close, 1)) / REF(close, 1) * 100
        
        # 计算交易线和短线
        terms = [
            26 * Q,
            25 * REF(Q, 1),
            24 * REF(Q, 2),
            23 * REF(Q, 3),
            22 * REF(Q, 4),
            21 * REF(Q, 5),
            20 * REF(Q, 6),
            19 * REF(Q, 7),
            18 * REF(Q, 8),
            17 * REF(Q, 9),
            16 * REF(Q, 10),
            15 * REF(Q, 11),
            14 * REF(Q, 12),
            13 * REF(Q, 13),
            12 * REF(Q, 14),
            11 * REF(Q, 15),
            10 * REF(Q, 16),
            9 * REF(Q, 17),
            8 * REF(Q, 18),
            7 * REF(Q, 19),
            6 * REF(Q, 20),
            5 * REF(Q, 21),
            4 * REF(Q, 22),
            3 * REF(Q, 23),
            2 * REF(Q, 24),
            REF(Q, 25)
        ]
        
        trading_line = sum(terms) / 351
        short_line = EMA(trading_line, 7)
        
        # 创建结果DataFrame，不使用日期时间
        result = pd.DataFrame({
            'datetime': range(len(klines)),  # 使用索引代替时间戳
            'open': open_price,
            'high': high,
            'low': low,
            'close': close,
            'trading_line': trading_line,
            'short_line': short_line
        })
        
        # 添加日期列和日期字符串列
        # 使用索引作为替代，但保持与原始数据相同的日期格式
        if hasattr(klines, 'datetime') and len(klines.datetime) > 0 and hasattr(klines.datetime.iloc[0], 'strftime'):
            # 如果原始数据有日期，使用原始日期
            result['date'] = klines.datetime
            result['date_str'] = [d.strftime('%Y-%m-%d') if hasattr(d, 'strftime') else str(d) for d in result['date']]
        else:
            # 否则使用索引
            result['date'] = [f"数据点-{i}" for i in range(len(result))]
            result['date_str'] = result['date']
        
        # 计算信号
        result['signal'] = np.nan  # 默认为NaN
        
        # 计算上穿下穿信号
        for i in range(2, len(result)):
            # 上穿信号: 前一周期trading_line < short_line，当前周期trading_line > short_line
            if (result.trading_line.iloc[i-1] < result.short_line.iloc[i-1] and 
                result.trading_line.iloc[i] > result.short_line.iloc[i]):
                result.loc[result.index[i], 'signal'] = '上穿'
            
            # 下穿信号: 前一周期trading_line > short_line，当前周期trading_line < short_line
            elif (result.trading_line.iloc[i-1] > result.short_line.iloc[i-1] and 
                  result.trading_line.iloc[i] < result.short_line.iloc[i]):
                result.loc[result.index[i], 'signal'] = '下穿'
    
    # 输出结果摘要
    print("\n计算完成! 结果摘要:")
    print(f"总K线数量: {len(result)}")
    print(f"上穿信号数量: {result['signal'].value_counts().get('上穿', 0)}")
    print(f"下穿信号数量: {result['signal'].value_counts().get('下穿', 0)}")
    
    # 显示最近的10个信号
    recent_signals = result[result['signal'].notna()].tail(10)
    if not recent_signals.empty:
        print("\n最近的10个信号:")
        for idx, row in recent_signals.iterrows():
            # 使用安全的日期格式化函数
            date_str = safe_date_format(row.get('date', row.get('datetime')))
            print(f"{date_str}: {row['signal']} (trading_line: {row['trading_line']:.2f}, short_line: {row['short_line']:.2f})")
    
    # 保存结果到CSV
    csv_filename = f"{symbol.replace('.', '_')}_{period_name}_信号.csv"
    result.to_csv(csv_filename, index=False)
    print(f"\n结果已保存到文件: {csv_filename}")

    # 使用PyEcharts创建可视化图表
    try:
        html_filename = create_pyecharts_visualization(result, symbol, period_name)
        print("\n" + "="*80)
        print(f"【可视化结果】PyEcharts交互式图表已生成")
        print(f"文件路径: {html_filename}")
        print(f"请在浏览器中打开此文件查看K线图和双均线金叉死叉分析图")
        print("-"*80)
        print(f"【信号统计】")
        print(f"金叉信号数量: {result['signal'].value_counts().get('上穿', 0)}")
        print(f"死叉信号数量: {result['signal'].value_counts().get('下穿', 0)}")
        print(f"总信号数量: {result['signal'].notna().sum()}")
        print(f"信号占比: {(result['signal'].notna().sum() / len(result) * 100):.2f}%")
        print("="*80)
    except Exception as e:
        print(f"生成可视化图表时出错: {e}")
        html_filename = None
    if html_filename:
        print(f"文件路径: {html_filename}")
        print(f"请在浏览器中打开此文件查看K线图和双均线金叉死叉分析图")
        print("-"*80)
        print(f"【信号统计】")
        print(f"金叉信号数量: {result['signal'].value_counts().get('上穿', 0)}")
        print(f"死叉信号数量: {result['signal'].value_counts().get('下穿', 0)}")
        print(f"总信号数量: {result['signal'].notna().sum()}")
        print(f"信号占比: {(result['signal'].notna().sum() / len(result) * 100):.2f}%")
        print("="*80)
    

    
    # matplotlib图表部分已被注释，我们使用pyecharts进行可视化
    # 如果需要使用matplotlib，请取消下面代码的注释
    """
    # 导入matplotlib
    import matplotlib.pyplot as plt
    
    plt.figure(figsize=(15, 10))
    
    # 绘制收盘价
    plt.subplot(2, 1, 1)
    # 使用索引作为X轴，避免日期转换问题
    plt.plot(result.index, result['close'], label='收盘价', color='black', alpha=0.6)
    plt.title(f"{symbol} {period_name} 收盘价")
    plt.legend()
    plt.grid(True)
    
    # 绘制均线和信号
    plt.subplot(2, 1, 2)
    plt.plot(result.index, result['trading_line'], label='Trading Line', color='blue')
    plt.plot(result.index, result['short_line'], label='Short Line', color='red')
    
    # 设置x轴刻度，每10个点显示一个日期标签
    if len(result) > 0:
        x_ticks = np.arange(0, len(result), max(1, len(result) # 10))
        plt.xticks(x_ticks)
    
    # 标记上穿和下穿点
    up_cross = result[result['signal'] == '上穿']
    down_cross = result[result['signal'] == '下穿']
    
    plt.scatter(up_cross.index, up_cross['trading_line'], color='green', marker='^', s=100, label='金叉')
    plt.scatter(down_cross.index, down_cross['trading_line'], color='red', marker='v', s=100, label='死叉')
    
    plt.title(f"{symbol} {period_name} 均线和信号")
    plt.legend()
    plt.grid(True)
    
    # 保存图表
    plt_filename = f"{symbol.replace('.', '_')}_{period_name}_图表.png"
    plt.savefig(plt_filename)
    print(f"Matplotlib静态图表已保存到文件: {plt_filename}")
    
    # 显示图表
    plt.tight_layout()
    plt.show()
    """
    
    print("已使用PyEcharts生成增强版可视化图表，包含K线图和双均线金叉死叉标注")
    
except Exception as e:
    print(f"发生错误: {e}")
    # 打印更详细的错误信息
    import traceback
    traceback.print_exc()
finally:
    print("处理完成")